﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ParallelMath.Library.AbstractDependencies;
using ParallelMath.Library.ConcreteDependencies;

namespace ParallelMath.Library
{
    public static class MatrixOperations1D
    {

        static MatrixOperations1D()
        {
            ThreadsCount=new ProcessorsBasedThreadsCount();
        }

        public static IThreadsCount ThreadsCount { get; set; }

        public static double[] GenerateRandom1DArray(long length, double minValue=0, double maxValue=1)
        {
            Random r = new Random();
            double[] generatedMatrix = new double[length];

            for (int i = 0; i < length; i++)
            {
                generatedMatrix[i] = r.NextDouble()*(maxValue - minValue) + minValue;
            }

            return generatedMatrix;
        }

       
        public static double[]  DoubleByANumber(this double[] array, double number, bool executeInOneThread = false)
        {
            if (Environment.ProcessorCount == 1 || executeInOneThread)
                return array.DoubleByANumberOneThread(number);


            long elementsInOnePart = array.LongLength / ThreadsCount.ThreadsCount;
            long extraElementsInFirstPart = array.LongLength % ThreadsCount.ThreadsCount;

            long startPosition = 0;

            Task[] tasks = new Task[ThreadsCount.ThreadsCount];
            for (int taskNum = 0; taskNum < ThreadsCount.ThreadsCount; taskNum++)
            {
                long elements = elementsInOnePart;
                if (taskNum == 0)
                {
                    elements += extraElementsInFirstPart;
                }
                long position = startPosition;
                Task task = Task.Factory.StartNew(() =>
                {
                    for (long i = position; i < elements; i++)
                    {
                        array[i] *= number;
                    }
                });

                tasks[taskNum] = task;
                startPosition += elements;
            }

            Task.WaitAll(tasks);

            return array;
        }

        public static double[] AddANumberOneThread(this double[] array, double number)
        {
            for (long i = 0; i < array.LongLength; i++)
            {
                array[i] = array[i]+number;
            }

            return array;
        }

        public static double[] AddANumber(this double[] array, double number, bool executeInOneThread = false)
        {
            if (Environment.ProcessorCount == 1 || executeInOneThread)
                return array.DoubleByANumberOneThread(number);


            long elementsInOnePart = array.LongLength / ThreadsCount.ThreadsCount;
            long extraElementsInFirstPart = array.LongLength % ThreadsCount.ThreadsCount;

            long startPosition = 0;

            Task[] tasks = new Task[ThreadsCount.ThreadsCount];
            for (int taskNum = 0; taskNum < ThreadsCount.ThreadsCount; taskNum++)
            {
                long elements = elementsInOnePart;
                if (taskNum == 0)
                {
                    elements += extraElementsInFirstPart;
                }
                long position = startPosition;
                Task task = Task.Factory.StartNew(() =>
                {
                    for (long i = position; i < elements; i++)
                    {
                        array[i] += number;
                    }
                });

                tasks[taskNum] = task;
                startPosition += elements;
            }

            Task.WaitAll(tasks);

            return array;
        }

        public static double[] AddOneThread(this double[] array, double[] array2)
        {
            if(array.LongLength!=array2.LongLength)
                throw new ArgumentException("The length of 2 arrays must be equal");

            double[] result=new double[array.LongLength];

            for (long i = 0; i < array.LongLength; i++)
            {
                result[i] = array[i] + array2[i];
            }

            return result;
        }

        public static double[] Add(this double[] array, double[] array2, bool executeInOneThread = false)
        {
            if (array.LongLength != array2.LongLength)
                throw new ArgumentException("The length of 2 arrays must be equal");

            if (Environment.ProcessorCount == 1 || executeInOneThread)
                return array.AddOneThread(array2);

            double[] result = new double[array.LongLength];

            long elementsInOnePart = array.LongLength / ThreadsCount.ThreadsCount;
            long extraElementsInFirstPart = array.LongLength % ThreadsCount.ThreadsCount;

            long startPosition = 0;

            Task[] tasks = new Task[ThreadsCount.ThreadsCount];
            for (int taskNum = 0; taskNum < ThreadsCount.ThreadsCount; taskNum++)
            {
                long elements = elementsInOnePart;
                if (taskNum == 0)
                {
                    elements += extraElementsInFirstPart;
                }
                long position = startPosition;
                Task task = Task.Factory.StartNew(() =>
                {
                    for (long i = position; i < elements; i++)
                    {
                        result[i] = array[i]+array2[i];
                    }
                });

                tasks[taskNum] = task;
                startPosition += elements;
            }

            Task.WaitAll(tasks);

            return result;
        }

        public static double[] DoubleByANumberOneThread(this double[] array, double number)
        {
            for (long i = 0; i < array.LongLength; i++)
            {
                array[i] = array[i] * number;
            }

            return array;
        }

    }
}
